#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#       feedmaker.py
#       
#		The MIT License
#		
#		Copyright (c) 2011 Clifton Chandler
#		
#		Permission is hereby granted, free of charge, to any person obtaining a copy
#		of this software and associated documentation files (the "Software"), to deal
#		in the Software without restriction, including without limitation the rights
#		to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#		copies of the Software, and to permit persons to whom the Software is
#		furnished to do so, subject to the following conditions:
#		
#		The above copyright notice and this permission notice shall be included in
#		all copies or substantial portions of the Software.
#		
#		THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#		IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#		FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#		AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#		LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#		OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#		THE SOFTWARE.

import xml.etree.ElementTree
import os
import sys
import glob
import time
import urllib
import operator
import argparse
import ConfigParser

# read a configuration file
def ReadConfig():
	global config
	config = ConfigParser.ConfigParser()

	# default streamripper options
	config.add_section("streamripper")
	config.set("streamripper", "stream_url", "http://yp.shoutcast.com/sbin/tunein-station.pls?id=1704166")
	config.set("streamripper", "time_to_rip", "7200")
	config.set("streamripper", "user_agent", "WinampMPEG/5.0")
	config.set("streamripper", "options", "")
	config.set("streamripper", "bin_location", "streamripper")

	# default media folder settings
	config.add_section("media")
	config.set("media","location","/path/to/media/files")
	config.set("media","max_folder_size","536870912")
	config.set("media","glob","*.mp3")
	config.set("media","mime_type","audio/mpeg")

	# default podcast xml options
	config.add_section("podcast")
	config.set("podcast","out_file","/path/to/podcast/file")
	config.set("podcast","title","Podcast Title")
	config.set("podcast","url_base","http://server/path/")
	
	try:
		conf_file_path = os.path.join( sys.path[0], args.conf_file )

		if args.write_conf_file == True:
			with open(args.conf_file_path, 'wb') as configfile:
				config.write(configfile)

		if ( os.path.exists( conf_file_path ) == True ):
			Log( LogLevel.INFO, "Reading config file " + conf_file_path + " ..." )
			config.read( conf_file_path )
		else:
			Log( LogLevel.ERROR, "Config file does not exist: " + args.conf_file )
			
				
		# Check media location
		path = config.get("media", "location")
		if os.path.exists( path ) == False:
			raise RuntimeError("Media location does not exist: " + path)
			
		# Check podcast output file
		path = config.get("podcast","out_file")
		path = os.path.dirname( path )
		if os.path.exists( path ) == False:
			raise RuntimeError("Podcast output path does not exist: " + path)
		
	except ConfigParser.Error, err:
		Log( LogLevel.ERROR, "Config file parser error: " + str(err) )
		exit(1)
	
	except RuntimeError, err:
		Log( LogLevel.ERROR, "Config file error: " + str(err) )
		exit(1)
		
	except IOError, err:
		Log( LogLevel.ERROR, "I/O Error: " + str(err) )
		exit(1)

# Rip the stream
def RipStream():
	# Streamripper command line options
	cmd_line = config.get("streamripper","bin_location") + " "          # add command to command line
	cmd_line += config.get("streamripper","stream_url")					# stream to rip
	cmd_line += " -d " + config.get("media","location")					# set dir for mp3 files
	cmd_line += " -s"													# don't create a directory for each stream
	cmd_line += " -u " + config.get("streamripper","user_agent")		# Set user agent	
	cmd_line += " -l " + config.get("streamripper","time_to_rip")		# amount of time to rip
	if args.verbose == False:
		cmd_line += " --quiet"											# don't show any output
	cmd_line += " --xs-none"											# don't do space detection
	cmd_line += " -q"													# add a sequence number to the mp3 files	
	cmd_line += " " + config.get("streamripper","options")				# add the additional options configured in the config file
		
	Log( LogLevel.DEBUG, "Calling ripper with command: " + cmd_line )
	
	os.system(cmd_line)

# Generate the mp3 file list
def GenFileList():
	# get file list
	flist = glob.glob( os.path.join( config.get("media","location"), config.get("media","glob") ) )
	
	# sort the file list
	for i in range(len(flist)):
		statinfo = os.stat(flist[i])
		flist[i] = flist[i],statinfo.st_size,statinfo.st_ctime

	flist.sort(key=operator.itemgetter(2), reverse=True)
	
	return flist

# Delete files
def DeleteFiles(file_list):
	# Calc folder size
	size = 0
	for file in file_list:
		size += file[1]
		
	del_index = len( file_list ) - 1
	while size > int(config.get("media","max_folder_size")):
		file_to_delete = file_list.pop()
		Log( LogLevel.DEBUG, "Deleting file: " + file_to_delete[0] )
		os.remove( file_to_delete[0] )
		size -= file_to_delete[1]

# in-place prettyprint formatter (from http://effbot.org/zone/element-lib.htm#prettyprint)
def Indent(elem, level=0):
    i = "\n" + level*"  "
    if len(elem):
        if not elem.text or not elem.text.strip():
            elem.text = i + "  "
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
        for elem in elem:
            Indent(elem, level+1)
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
    else:
        if level and (not elem.tail or not elem.tail.strip()):
            elem.tail = i

# Generate the XML podcast file
def GenXmlFile(file_list):
	import xml.etree.ElementTree as ET

	# build rss feed structure
	rss = ET.Element("rss")
	rss.set("xmlns:itunes", "http://www.itunes.com/dtds/podcast-1.0.dtd")
	rss.set("version", "2.0")

	# channel element
	channel = ET.SubElement(rss, "channel")
	
	# Add title	
	title = ET.SubElement(channel, "title")
	title.text = config.get("podcast","title")
	
	# Add description
	desc = ET.SubElement(channel, "description")
	desc.text = "Feedmaker Podcast"
	
	# Loop through the mp3 files
	for file in file_list:
		Log( LogLevel.DEBUG, "Adding: " + file[0] )
		
		# determine url of file
		url = config.get("podcast","url_base") + urllib.quote( os.path.basename(file[0]) )
		
		# Add an item
		item = ET.SubElement(channel, "item")
		
		item_title = ET.SubElement(item, "title")
		item_title.text = os.path.basename(file[0])
		
		item_enclosure = ET.SubElement(item, "enclosure")
		item_enclosure.set("url", url)
		item_enclosure.set("type", config.get("media","mime_type"))
	
		item_guid = ET.SubElement(item, "guid")
		item_guid.text = url	
		
		item_pubdate = ET.SubElement(item, "pubDate")
		item_pubdate.text = time.ctime(os.path.getctime(file[0]))

	# cleanup the tree
	Indent(rss)		
	# wrap it in an ElementTree instance
	tree = ET.ElementTree(rss)
	# write to XML file
	tree.write(config.get("podcast","out_file"))

# Parse the command line options
def ParseArgs():
	global args
	
	default_config_path = os.path.join( sys.path[0], "feedmaker.conf" )
	
	# Set up the argument parser
	parser = argparse.ArgumentParser(description='Create podcast from radio stream.')
	parser.add_argument('-x', action='store_true', default=False, dest='xml_only', help='Generate the XML file only')
	parser.add_argument('-v', action='store_true', default=False, dest='verbose', help='Verbose')
	parser.add_argument('-q', action='store_true', default=False, dest='quiet', help='Quiet mode')
	parser.add_argument('-c', action='store', default=default_config_path, dest='conf_file', help='Config file')
	parser.add_argument('-w', action='store_true', default=False, dest='write_conf_file', help='Write the config file with defaults')
	
	# Parse the args
	args = parser.parse_args()

# Define loging level
class LogLevel:
	ERROR=1
	INFO=2
	DEBUG=3
	
# Log to the console using different log levels
def Log(level,log_string):
	if level==LogLevel.ERROR:
		print "ERROR - " + log_string
	elif level==LogLevel.INFO:
		if args.quiet == False:
			print log_string
	elif level==LogLevel.DEBUG:
		if args.verbose == True:
			print log_string
	else:
		raise Exception("Unknown logging level")
	
# main entry point
def main():
	
	# Parse the argument
	ParseArgs()
	
	# Read the config file
	ReadConfig()
	
	# Rip the stream
	if args.xml_only == False:
		Log( LogLevel.INFO, "Ripping stream ..." )
		RipStream()
	
	# Get the MP3 list
	Log( LogLevel.INFO, "Getting file list from folder " + config.get("media","location") + " ..." )
	flist = GenFileList()
	
	# Remove old files
	Log( LogLevel.INFO, "Deleting old files ..." )
	DeleteFiles(flist)
	
	# Generate the XML file
	Log( LogLevel.INFO, "Generating " + config.get("podcast","out_file") + " ..." )
	GenXmlFile(flist)	
	
	Log( LogLevel.INFO, "Done." )
	return 0

if __name__ == '__main__':
	main()
